import sys 
from string import atoi
import struct

output = sys.stdout 

DEBUG = False

pwd = None

# we're only doing one fat filesystem at a time so a single global is ok 
fatfd = None   

# associative store of blocks.
# you don't have to do it, but you should ask yourself what you'd do
# if there were a fixed number of blocks you could use in this

fatblocks = {}  

# to make this truly portable this should be read from the boot sector
# but we're just going to assume that sectorSize is always 512 
sectorSize = 512

# size of one directory entry 

dirSize = 32 

# number of sectors per cluster

secPerClust = 1

# execfile is like import, but loads everything to this namespace 

execfile("autofuncs.py") 

def hton(intval, size) :
    res = ""
    for i in range(size):
        next = intval & 0xFF
        intval = intval >> 8
        res =  res + chr(next) # chr(int) returns ascii of int
    return res

def ntoh(str, size) :
    res = 0
    for i in range(size-1,-1,-1) : # from size-1, end, stride
        res = (res << 8) + abs(ord(str[i]))
    return res

# load a fat for rw access 
def loadfat(args) :
    openfat(args[1], "r+") 

# load a fat for read only access 
def readfat(args) :
    openfat(args[1], "r") 

def openfat(fn, how) : 
    global fatfd
    global fatblocks
    global pwd
    fatblocks = {}  # in case we had another file open 
    try : 
        fatfd = open(fn, how)
        # ensures block 0 is read in 
        # and the right number of sectors per block is known
        secPerClust = get_sectors_per_cluster(block(0))
        pwd = clusterStartForFatNumbered(get_root_start_cluster(block(0)))
    except :
        print "unable to open", fn 


def block(n) :
    global fatfd
    if fatfd == None :
        print "can't load from non-opened fat"         
        return 

    if n not in fatblocks :
        fatfd.seek(n*sectorSize)
        fatblocks[n] = fatfd.read(sectorSize) 
    return fatblocks[n] 

## NEW
def writeBlock(n, data) :
    fatblocks[n] = data

## NEW
def save(args) :
    global fatfd
    if fatfd == None :
        print "can't save to non-opened fat"         
        return 

    for i in fatblocks.keys() :
        fatfd.seek(i*sectorSize)
        fatfd.write(fatblocks[i])

## NEW
def showClusters(args) :
    clustNum = atoi(args[1])
    clustNum, fatStart, start = clusterNumbersFromAbsolute(clustNum)
    output.write("File start FAT entry %d\n" % start)
    currentBlock = block(fatStart)
    while True :
        entry = ntoh(currentBlock[start*4:],4)
        if entry > 0x0ffffff6 : # anything greater is a unused sector
            break
        start += 1
        output.write("%x\n" % entry)

## NEW
# This will return a tuple containing
# the absolute cluster number
# Fat block
# Offset within the fat block
def clusterNumbersFromAbsolute(clustNum) :
    fatOffset = clustNum*4
    fatSecNum = get_reserved_sectors(block(0)) + (fatOffset/sectorSize)
    fatEntryOffset = clusterStartToFatNumber(clustNum)

    return clustNum, fatSecNum, fatEntryOffset

## NEW
def clusterNumbersFromReletive(clustNum) :
    cluster = clusterStartForFatNumbered(clustNum)
    return clusterNumbersFromAbsolute(cluster)

## NEW
# Takes a cluster number for a directory
# looks for fileName then
# returns the absolute cluster of that file
def fileCluster(clustNum, fileName) :
    start = clustNum
    dirdata = getDirData(start)
    for j in range(len(dirdata)/dirSize) : 
        fstart = j*dirSize 
        if ord(dirdata[fstart]) != 0 :
            file = dirListing(dirdata[fstart:fstart+dirSize])
            if file[0] == fileName.upper() :
                return file[1]

## NEW
def setDir(args) :
    global pwd
    pwd = parsePath(args[1])
    if(DEBUG) : print "current dir is: ", pwd

## NEW
def parsePath(path) :
    global pwd
    pathDirs = path.split('/')
    cluster = pwd
    if pathDirs[0] == '' :
        cluster = clusterStartForFatNumbered(get_root_start_cluster(block(0)))
        if(DEBUG) : print "start at root"
        pathDirs.remove('')
    for i in range(len(pathDirs)) :
        if(DEBUG) : print pathDirs[i]
        if isDir(cluster, pathDirs[i]) :
            cluster = fileCluster(cluster,pathDirs[i])
            if(DEBUG) : print "cluster: ", cluster
    return cluster



## NEW
def isDir(cluster, name) :
    if(name == '') : return False

    dirEntry = dirEntryByName(cluster, name.upper())

    attr = get_file_attributes(dirEntry)
    if (attr & 0x10) == 0x10 :
        return True
    return False


## NEW
def dirEntryByName(clustNum, fileName) :
    start = clustNum
    dirdata = getDirData(start)
    for j in range(len(dirdata)/dirSize) : 
        fstart = j*dirSize 
        if ord(dirdata[fstart]) != 0 :
            file = dirListing(dirdata[fstart:fstart+dirSize])
            if file[0] == fileName :
                return dirdata[fstart:fstart+dirSize]


    
#Logical Byte Addressing - maps a FAT entry to cluster(sector)# ie - FAT entry 2 maps to the root directory (sector 64)
#maps relative cluster number to absolute sector#
def clusterStartForFatNumbered(n) :
    spf = get_sectors_per_fat_32(block(0))
    fc = get_fat_count(block(0))
    rs = get_reserved_sectors(block(0)) 
    return (n-2) + rs + fc*spf

## NEW
def clusterStartToFatNumber(n) :
    spf = get_sectors_per_fat_32(block(0))
    fc = get_fat_count(block(0))
    rs = get_reserved_sectors(block(0)) 
    return (n - rs - fc*spf) + 2

## NEW
def listDir(args) :
    dirdata = getDirData(parsePath(args[1]))

    for j in range(len(dirdata)/dirSize) : 
        fstart = j*dirSize 
        if ord(dirdata[fstart]) != 0 :
            file = dirListing(dirdata[fstart:fstart+dirSize]) 
            output.write("%s\t%d\n" % (file[0], file[2]))

## NEW
def EntryIsFile(dirEntry, fileName) :
    file = dirListing(dirEntry) 
    if file[0] == fileName.upper() :
        return True

    return False


## NEW
def fileInfo(args) :
    dirdata = getDirData(pwd)

    for j in range(len(dirdata)/dirSize) : 
        fstart = j*dirSize 
        if ord(dirdata[fstart]) != 0 :
            if EntryIsFile(dirdata[fstart:fstart+dirSize], args[1]) :
                dumpDirEntryInfo(dirdata[fstart:fstart+dirSize]) 
    

def dumpRootDirInfo(args) :
    start = clusterStartForFatNumbered(get_root_start_cluster(block(0)))
    output.write("start dir at %d in: %d (0x%x) bytes\n" %
                 (start,start*sectorSize, start*sectorSize))

    dirdata = getDirData(start)

    for j in range(len(dirdata)/dirSize) : 
        fstart = j*dirSize 
        if ord(dirdata[fstart]) == 0 :
            output.write("no file at %d\n"% fstart) 
        else : 
            dumpDirEntryInfo(dirdata[fstart:fstart+dirSize]) 

## NEW
def getDirData(start) :
    dirdata = ""
    for i in range(get_sectors_per_cluster(block(0))) :
        dirdata += block(start+i) 

    return dirdata

## NEW
# returns the file/dir name and absolute cluster number
# provided directory entry
def dirListing(dirEntry) :
    cluster = get_high_cluster_bytes(dirEntry)
    cluster = cluster << 2
    cluster = cluster | get_low_cluster_bytes(dirEntry)

    # Root is thought to be 0, by .., but this is not really the case
    if cluster == 0 : cluster = clusterStartForFatNumbered(get_root_start_cluster(block(0)))

    name = get_file_name(dirEntry).strip()
    if len(get_file_ext(dirEntry).strip()) != 0 :
        name = name + "." + get_file_ext(dirEntry).strip()

    return name, clusterStartForFatNumbered(cluster), get_file_size(dirEntry)

## NEW
def dirCluster(args) :
    dirdata = getDirData(atoi(args[1]))

    fstart = dirSize

    for j in range(len(dirdata)/dirSize) : 
        fstart = j*dirSize 
        if ord(dirdata[fstart]) != 0 :
            file = dirListing(dirdata[fstart:fstart+dirSize])
            output.write("%s\n" % file[0]);


def showFat(args) :
    fatStart = get_reserved_sectors(block(0))
    fatLen   = get_sectors_per_fat_32(block(0)) 
    output.write("show fat start=%s len=%s\n" % (fatStart, fatLen)) 

    for i in range(fatLen) :
        output.write("fat block= %d\n" % (fatStart+i))
        currentBlock = block(fatStart+i)
        for j in range(sectorSize/4) :
            entry = ntoh(currentBlock[j*4:],4)
            if entry != 0 : 
                output.write("%04d[%06d--%08x]->%08x\n" % (i, j,j, entry)) 


def setOutput(args) :
    global output  
    if len(args) == 1 :
        if output != sys.stdout :
            output.close() 
            output = sys.stdout
    else : 
        output = open(args[1], "w") 

def dofile(args) :
    try : 
        inputFile = open(args[1], "r")
        runInterp(inputFile)
        inputFile.close() 
    except Exception, e : 
        if e.args[0] == "quitting" :
            return
        else : 
            print "Can't execute ", args[1] 

def say(args) :
    outstring = "" 
    outstring = " ".join(args[1:])
    output.write(outstring + "\n") 

commands = {"say":say, 
            "set-output":setOutput, 
            "do-file":dofile,
            "load":loadfat,
            "read":readfat,
            "quit":quit,
            "show-boot":dumpFatInfo,
            "show-fat":showFat,
            "show-root":dumpRootDirInfo,
            "dir-cluster":dirCluster,
            "show-clusters":showClusters,
            "set-dir":setDir,
            "dir":listDir,
            "show-file-info":fileInfo
            }     

def doCommand(line) : 
    args = line.split()

    if len(args) > 0 :
        # I wanted to add comments to my test files, this will ignore
        # everything after # :)
        if args[0].find("#") >= 0 :
            return

        i = 1
        while i < len(args) :
            if args[i].find("#") >= 0 :
                break
            i += 1

        if args[0] in commands :
            commands[args[0]](args[:i]) 
        else :
            print("unknown command: ", args[0])

def runInterp(inFile) :
    while True : 
        try : 
            line = inFile.readline() 
            if not line : break 
            doCommand(line) 
        except Exception, e :  # keep on going till it's time to quit
            if e.args[0] == "quitting" :
                return
        else : 
            pass 

def main() :
    runInterp(sys.stdin) 

if __name__ == "__main__" : 
    main()
